﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace LightEngine
{
    namespace MessbauerCore
    {
        namespace MessbauerData
        {
            /// <Описание перечисления public enum AllowableNumOfChannels>
            /// Перечисление содержит возможные режимы измерения/отображения спектра,
            /// всего 5 режимов измерения мессбауэровских спектров: на 256, 512, 1024, 2048 и 4096 точек.
            /// </Описание перечисления public enum AllowableNumOfChannels>
            public enum AllowableNumOfChannels
            {
                Channel_Mode1 = 256,
                Channel_Mode2 = 512,
                Channel_Mode3 = 1024,
                Channel_Mode4 = 2048,
                Channel_Mode5 = 4096
            };
            /// <<Описание перечисления public enum NumberOfLines>
            /// Перечисление содержит возможное число линий в мессбауэровском спектре
            /// </Описание перечисления public enum NumberOfLines>
            public enum NumberOfLines
            {
                Singlet = 1,
                Doublet = 2,
                Sextet = 6
            };

            /// <Описание интерфейса IMeasureInterface>
            /// Интерфейс содержит функции, предназначенные для выполнения измерения спектра (записи
            /// числа импульс в принимающий буфер).
            /// Элементы интерфейса:
            /// 1. Методы (функции-члены)
            /// а) Boolean AddCountsToChannel(UInt16 Channel, UInt64 Counts);
            ///    Функция выполняет добавление Counts счетных импульсов в ячейку памяти
            ///    с номером Channel, значения которого лежат в диапазоне 0-4095(2^13).
            ///    Возвращает true если счетные импульсы добавлены, иначе возвращает false.
            /// б) Boolean AddCounts(UInt64[] Counts);
            ///    Функция выполняет добавление всего массива отчетов Counts в соответсвующие
            ///    ячейки памяти (если размер массива соответсвует ожидаемой величине).
            ///    Если импульсы добавлены возвращает true.
            /// в) void    SetMeasureMode(UInt16 NumberOfChannels) функция устанавливает
            ///    режи измерений (число точек в спектре).
            ///    Ничего не возвращает: тип void.
            /// г) void ReplaceChannelsCounts(UInt64[] Counts) функция предназначена для обновления с
            ///    заменой значений числа импульсов в каналах. Если обновление выполнено возвращает 
            ///    true.
            /// </Описание интерфейса IMeasureInterface>
            public interface IMeasureInterface
            {
                Boolean AddCountsToChannel(UInt16 Channel, UInt64 Counts);
                Boolean AddCounts(UInt64[] Counts);
                void SetMeasureMode(UInt16 NumberOfChannels);
                Boolean ReplaceChannelsCounts(UInt64[] Counts);
            }

            /// <описание класса MessbSpectrum>
            /// Версия класса 1.1
            /// Автор(ы): Ушаков Михаил
            /// Класс предназначен для хранения Мессбауэровского спектра, вычисления параметров
            /// спектра, продолжение дальнейших измерений
            /// Элементы класса:
            /// 1. Константы класса:
            /// а) UInt16 NumOfPointsForBaseLine = 20; Число точек спектра (с 0 ячейки памяти)
            ///    используемое для расчета базовой линии.
            /// б) UInt16 iSeriesOfSteps = 10; Серия повторяющихся событий, испольуется для поиска 
            ///    пиков спектра.
            /// 2. Поля класса:
            /// а) UInt64[] SpectrumData = new UInt64[Channel_Mode5] ячейки памяти, хранящие спектр.
            /// б) UInt16 ActualNumOfChannels = 0; чило каналов в спектре (используемое), дело в том что
            ///    SpectrumData содержит максимальное число ячеек памяти, однако работа может идти со 
            ///    спектром с меньшим количеством точек.
            /// в) UInt64 BaseLineValue = 0; Базовая линия спектра.
            /// г) UInt64   MaximumNumOfCounts Максимальное число импульсов в точках спектра.
            /// д) UInt64   MinimumNumOfCounts Минимальное число импульсов в точках спектра.
            /// е) Double   SpectrumSignalNoiseRati Отношение сигнал шум в спектре.
            /// ж) Double   SpectrumEffectValue Величина эффекта спектра
            /// з) UInt16[] Lines массив координат центров тяжести мессбауэровских мессбауэровских пиков.
            /// и) UInt16   ActualNumOfLines число мессбауэровских пиков.
            /// 3. Методы класса:
            /// а) public MessbSpectrum() конструктор класса.
            /// б) public void AddCountsToCurrentChannel(UInt16 Channel, UInt64 Counts) добавление Counts
            ///    импульсов в канал Channel.
            /// в) public void AddWholeArrayOfCounts(UInt64[] Counts) добавляет массив точек к SpectrumData,
            ///    число используемых каналов ActualNumOfChannels должно быть равно размеру массива Counts
            ///    в данной версии класса это не проверяется.
            /// г) public void InitMeasureSettings(UInt16 NumOfChannels) функция задает режим измерений по
            ///    числу точек(каналов) в спектре
            /// д) public void LoadSpectrum(UInt64[] Spectrum, UInt16 ActLenght) загрузка Spectrum в 
            ///    SpectrumData с числов используемых точек ActLenght.
            /// е) public void CalculateParameters(Boolean EnablePointsCorrection) функция выполняет вычисление 
            ///    параметров мессбауэровского спектра, записанного в SpectrumData и коррекцию выбросов если
            ///    EnablePointsCorrection равно true.
            /// ж) public UInt64[] GetSpectrumPoints() возвращает массив точек, что необходимо для 
            ///    проведения преобразований, рисования и т.д.
            /// 4. Свойства и индексаторы класса:
            /// а) public UInt16 NumOfChannels свойство только для чтения, возвращает число  каналов в 
            ///    мессбауэровском спектре (число используемых точек).
            /// б) public UInt16 NumOfLines свойство только для чтения, возвращает число линий в спектре
            ///    т.е. тип спектра (синглет, дублет или секстет) на данный момент НЕ РЕАЛИЗОВАНО.
            /// в) public UInt64 MinNumOfCounts свойство только для чтения, возвращает минимальное число 
            ///    импульсов в спектре SpectrumData.
            /// г) public UInt16 MinNumOfCountsChannel свойство только для чтения, возвращает номер канала 
            ///    в котором минимальное число импульсов.
            /// д) public UInt64 MaxNumOfCounts свойство только для чтения, возвращает максимальное число
            ///    импульсов в спектре.
            /// е) public UInt16 MaxNumOfCountsChannel свойство только для чтения, возвращает номер канала
            ///    с максимальным числом импульсов.
            /// ж) public UInt64 BaseLine свойство только для чтения, возвращает число импульсов на базовой 
            ///    линии спектра.
            /// з) public UInt64 this[UInt16 Channel] индексатор только для чтения, возвращает число 
            ///    импульсов в канале Channel, если Channel меньше ActualNumOfChannels-1, в противном случае
            ///    возвращает 0.
            /// и) public Double EffectValue свойство только для чтения, возвращает величину эффекта спектра
            ///    в относительных еденицах.
            /// к) public Double SignalNoiseRatio свойство только для чтения, возвращает соотношение 
            ///    сигнла/шум в спектре.
            /// </описание класса MessbSpectrum>
            public class MessbSpectrum : IMeasureInterface
            {
                // константы
                private const UInt16 NumOfPointsForBaseLine = 20;
                private const UInt16 SeriesOfSteps = 10;
                // поля
                private UInt64[] SpectrumData = new UInt64[(UInt16)AllowableNumOfChannels.Channel_Mode5];    // массив содержащий спектр
                private UInt16 ActualNumOfChannels = 0;
                private UInt64 BaseLineValue = 0;
                private UInt64 MaximumNumOfCounts = 0;
                private UInt16 ChannelWithMaximumNumOfCounts = 0;
                private UInt64 MinimumNumOfCounts = 0;
                private UInt16 ChannelWithMinimumNumOfCounts = 0;
                private Double SpectrumSignalNoiseRatio = 0;
                private Double SpectrumEffectValue = 0;
                private UInt16[] Lines = new UInt16[(UInt16)NumberOfLines.Sextet];
                private UInt16 ActualNumOfLines = 0;
                // методы
                public MessbSpectrum() { }

                public Boolean AddCountsToChannel(UInt16 Channel, UInt64 Counts)
                {
                    if (Channel < ActualNumOfChannels)
                    {
                        SpectrumData[Channel] += Counts;
                        return true;
                    }
                    return false;
                }

                public Boolean AddCounts(UInt64[] Counts)
                {
                    if (Counts.Length != ActualNumOfChannels)
                        return false;
                    for (UInt16 Counter = 0; Counter < Counts.Length; Counter++)
                        SpectrumData[Counter] += Counts[Counter];
                    return true;
                }

                public void SetMeasureMode(UInt16 NumOfChannels)
                {
                    ActualNumOfChannels = NumOfChannels;
                }

                public Boolean ReplaceChannelsCounts(UInt64[] Counts)
                {
                    if (Counts.Length != ActualNumOfChannels)
                        return false;
                    for (UInt16 Counter = 0; Counter < Counts.Length; Counter++)
                        SpectrumData[Counter] = Counts[Counter];
                    return true;
                }

                public void LoadSpectrum(UInt64[] Spectrum, UInt16 NumberOfChannels)
                {
                    ActualNumOfChannels = NumberOfChannels;
                    SpectrumData = Spectrum;
                }

                public void CalculateParameters(Boolean EnablePointsCorrection)
                {
                    if (ActualNumOfChannels == 0) return;
                    if (EnablePointsCorrection == true)
                        BlowOutCorrection.CorrectBlowOutPoints(SpectrumData, ActualNumOfChannels);

                    MaximumNumOfCounts = SpectrumData.Max();
                    ChannelWithMaximumNumOfCounts = (UInt16)Array.IndexOf<UInt64>(SpectrumData, MaximumNumOfCounts);

                    var Points = from SpectrumPoint in SpectrumData
                                 where SpectrumPoint > 0
                                 select SpectrumPoint;
                    MinimumNumOfCounts = Points.ToArray<UInt64>().Length == 0 ? 0 : Points.Min();
                    if (MinimumNumOfCounts == 0) return;
                    ChannelWithMinimumNumOfCounts = (UInt16)Array.IndexOf<UInt64>(SpectrumData, MinimumNumOfCounts);

                    BaseLineValue = 0;

                    var BaseLineSum = SpectrumData.Where((SpectrumPoint, index) => index < NumOfPointsForBaseLine)
                                                  .Sum<UInt64>((Element) => (double)Element) / (double)NumOfPointsForBaseLine;
                    BaseLineValue = (UInt64)BaseLineSum;

                    var StartPoints = SpectrumData.Where((SpectrumPoint, index) => index < NumOfPointsForBaseLine);
                    SpectrumEffectValue = (Double)(BaseLineValue - MinNumOfCounts) / (Double)BaseLineValue;
                    SpectrumSignalNoiseRatio = (Double)(BaseLineValue - MinNumOfCounts) / (Double)(StartPoints.Max() - StartPoints.Min());
                }

                public UInt64[] GetSpectrumPoints()
                {
                    UInt64[] RealSpectrum = new UInt64[ActualNumOfChannels];
                    Array.Copy(SpectrumData, 0, RealSpectrum, 0, ActualNumOfChannels);
                    return RealSpectrum;
                }

                // индексаторы и свойства
                public UInt16 NumOfChannels
                {
                    get { return ActualNumOfChannels; }
                }

                public UInt16 NumOfLines
                {
                    get { return ActualNumOfLines; }
                }

                public UInt64 MinNumOfCounts
                {
                    get { return MinimumNumOfCounts; }
                }

                public UInt16 MinNumOfCountsChannel
                {
                    get { return ChannelWithMinimumNumOfCounts; }
                }

                public UInt64 MaxNumOfCounts
                {
                    get { return MaximumNumOfCounts; }
                }

                public UInt16 MaxNumOfCountsChannel
                {
                    get { return ChannelWithMaximumNumOfCounts; }
                }

                public UInt64 BaseLine
                {
                    get { return BaseLineValue; }
                }

                public UInt64 this[UInt16 Channel]
                {
                    get
                    {
                        if (Channel < ActualNumOfChannels)
                        {
                            return SpectrumData[Channel];
                        }
                        else return 0;
                    }
                }

                public Double EffectValue
                {
                    get { return SpectrumEffectValue; }
                }

                public Double SignalNoiseRatio
                {
                    get { return SpectrumSignalNoiseRatio; }
                }
            }

            /// <описание класса public class BlowOutCorrection>
            /// Версия класса 1.0
            /// Автор(ы): Ушаков Михаил
            /// Статический класс, предназначенный для исправления выбросов на мессбауэровских спектрах.
            /// 1. Константы класса:
            /// а) private const UInt16 NumberOfPointsToFindPreliminaryBaseLine = 20; число точек, 
            ///    используемое для поиска предварительно базовой линии.
            /// б) private const UInt16 MinimumNumberOfPointsToCalculateBaseLine = 4; число точек,
            ///    используемое для вычисления базовой линии.
            /// 2. Поля класса:
            ///    отсутствуют.
            /// 3. Методы класса:
            /// а) private static Double PointStatisticalDeviation(UInt64 SpectrumPoint) возвращает 
            ///    величину коридора(стат разброса), равную 1,5*sqrt(2*SpectrumPoint) точки с числом 
            ///    импульсов SpectrumPoint.
            /// б) private static void CorrectBaseLinePoint(UInt64 BaseLine, ref UInt64 SpectrumPoint)
            ///    функция для коррекции точки SpectrumPoint, принадлежащей базовой линии на попадание 
            ///    ее на базовую линию плюс-минус коридор стат. разброса.
            /// в) private static void CheckForUpperLimit(UInt64 BaseLine, ref UInt64 SpectrumPoint) функция,
            ///    проверяующая, что точка SpectrumPoint не лежит выше чем максимально возможная величина 
            ///    числа импульсов в спектре (базовая линия + коридор стат. разброса).
            /// г) public static void CorrectBlowOutPoints(UInt64[] MessbSpectrum, UInt16 NumOfChannels) функция
            ///    выполняет коррекцию мессбауэровского спектра с числом точек NumOfChannels по устранению
            ///    выбросов в спектре.
            /// 4. Свойства и индексаторы класса:
            ///    отсутствуют.
            /// </описание класса public class BlowOutCorrection>

            public class BlowOutCorrection
            {
                private const UInt16 NumberOfPointsToFindPreliminaryBaseLine = 20;
                private const UInt16 MinimumNumberOfPointsToCalculateBaseLine = 4;
                //private const UInt16 InitialNumOfPoints = 3;

                private static Double PointStatisticalDeviation(UInt64 SpectrumPoint)
                {
                    return 1.5 * Math.Sqrt(2 * (Double)(SpectrumPoint));
                }

                private static void CorrectBaseLinePoint(UInt64 BaseLine, ref UInt64 SpectrumPoint)
                {
                    if (SpectrumPoint > BaseLine + PointStatisticalDeviation(BaseLine) ||
                        SpectrumPoint < BaseLine - PointStatisticalDeviation(BaseLine))
                    {
                        SpectrumPoint = BaseLine;
                    }
                }

                private static void CheckForUpperLimit(UInt64 BaseLine, ref UInt64 SpectrumPoint)
                {
                    if (SpectrumPoint > BaseLine + PointStatisticalDeviation(BaseLine))
                    {
                        SpectrumPoint = BaseLine;
                    }
                }

                public static void CorrectBlowOutPoints(UInt64[] MessbSpectrum, UInt16 NumOfChannels)
                {
                    //1. Находим первую точку не являющуюся выбросом и вычисляем предварительное значение базовой линии ...
                    UInt64[] BaseLineArray = new UInt64[NumberOfPointsToFindPreliminaryBaseLine];
                    Double[] PointsDeviationOfBaseLine = new Double[NumberOfPointsToFindPreliminaryBaseLine];
                    UInt16[] PointsSuitability = new UInt16[NumberOfPointsToFindPreliminaryBaseLine];
                    UInt64 BaseLine = 0;
                    // Копируем по NumberOfPointsToFindPreliminaryBaseLine / 2 == 10 точек с начала и конца массива
                    for (UInt16 Counter = 0; Counter < NumberOfPointsToFindPreliminaryBaseLine / 2; Counter++)
                    {
                        BaseLineArray[Counter] = MessbSpectrum[Counter];
                        BaseLineArray[Counter + NumberOfPointsToFindPreliminaryBaseLine / 2] = MessbSpectrum[NumOfChannels - NumberOfPointsToFindPreliminaryBaseLine / 2 + Counter];
                    }
                    // Для каждой точки вычисляем 1,5*sqrt(2*Число импульсов)
                    for (UInt16 Counter = 0; Counter < NumberOfPointsToFindPreliminaryBaseLine; Counter++)
                    {
                        PointsDeviationOfBaseLine[Counter] = PointStatisticalDeviation(BaseLineArray[Counter]);
                    }
                    UInt16 LocalCounter = 0;
                    //Находим стат разброс в который попадает максимальное количество точек
                    for (UInt16 IndexCounter = 0; IndexCounter < NumberOfPointsToFindPreliminaryBaseLine; IndexCounter++)
                    {
                        for (UInt16 Counter = 0; Counter < NumberOfPointsToFindPreliminaryBaseLine; Counter++)
                        {
                            if (BaseLineArray[Counter] < BaseLineArray[IndexCounter] + PointsDeviationOfBaseLine[IndexCounter] &&
                                BaseLineArray[Counter] > BaseLineArray[IndexCounter] - PointsDeviationOfBaseLine[IndexCounter])
                            {
                                LocalCounter++;
                            }
                        }
                        PointsSuitability[IndexCounter] = LocalCounter;
                        LocalCounter = 0;
                        //MessageBox.Show(PointsSuitability[IndexCounter].ToString());
                    }
                    UInt16 MaxNumOfPoints = 0, Index = 0;
                    for (UInt16 Counter = 0; Counter < NumberOfPointsToFindPreliminaryBaseLine; Counter++)
                    {
                        if (PointsSuitability[Counter] > MaxNumOfPoints)
                        {
                            MaxNumOfPoints = PointsSuitability[Counter];
                            Index = Counter;
                        }
                    }
                    // Если число точек стат разброса меньше MinimumNumberOfPointsToCalculateBaseLine нет смысла корректировать спектр, это шум
                    if (MaxNumOfPoints < MinimumNumberOfPointsToCalculateBaseLine)
                        return;
                    LocalCounter = 0;
                    for (UInt16 Counter = 0; Counter < NumberOfPointsToFindPreliminaryBaseLine; Counter++)
                    {
                        if (MessbSpectrum[Counter] < BaseLineArray[Index] + PointsDeviationOfBaseLine[Index] &&
                            MessbSpectrum[Counter] > BaseLineArray[Index] - PointsDeviationOfBaseLine[Index])
                        {
                            LocalCounter++;
                            BaseLine += MessbSpectrum[Counter];
                        }
                    }
                    BaseLine = (UInt64)((Double)BaseLine / (Double)LocalCounter);

                    CorrectBaseLinePoint(BaseLine, ref MessbSpectrum[0]);
                    CorrectBaseLinePoint(BaseLine, ref MessbSpectrum[1]);
                    CorrectBaseLinePoint(BaseLine, ref MessbSpectrum[NumOfChannels - 2]);
                    CorrectBaseLinePoint(BaseLine, ref MessbSpectrum[NumOfChannels - 1]);

                    for (UInt16 Counter = 2; Counter < NumOfChannels - 1; Counter++)
                    {
                        CheckForUpperLimit(BaseLine, ref MessbSpectrum[Counter]);
                    }

                }
            }
        }
    }
}